package controller

import (
	"context"
	"fmt"
	"time"

	"v8s/compose/model"
	"v8s/logger"
	resourcename "v8s/resource-name"

	"v8s-controller/internal"
	"v8s-controller/internal/configuration"
	"v8s-controller/internal/db"
	"v8s-controller/internal/logging"
)

type (
	SynchronizationLoggingController struct {
		*generic

		database   *db.ProjectsRepository
		logging    *logging.LoggingRepository
		properties configuration.LoggingProperties
	}
)

const (
	projectsLoggingControllerName = "logging-controller"

	seenLoggingTTL = time.Second * 10
)

var (
	withoutLogStatuses = []db.LoggingStatus{db.ErrorLoggingStatus, db.SuccessLoggingStatus}
)

func CreateSynchronizationLoggingController(
	database *db.ProjectsRepository,
	logging *logging.LoggingRepository,
	properties configuration.LoggingProperties,
) *SynchronizationLoggingController {
	return &SynchronizationLoggingController{
		generic: &generic{
			log: logger.Logger().Named(projectsLoggingControllerName).Sugar(),
		},
		logging:    logging,
		database:   database,
		properties: properties,
	}
}

func (p *SynchronizationLoggingController) Name() string {
	return projectsControllerName
}

func (p *SynchronizationLoggingController) Execute(ctx context.Context) error {
	startedAt := time.Now().Add(-seenLoggingTTL)

	p.log.Infof("synchronization of tasks seen before '%s'", startedAt)

	return p.withChannel(p.database.ListProjectsWithAllLogsTaskStatusesNotInSince(ctx, 20, withoutLogStatuses, startedAt), func(project *db.Project) (bool, error) {
		changedTasks := map[internal.TaskID]db.Task{}

		for _, task := range project.Tasks {

			updatedTask, handled, err := p.handle(ctx, &project.Cred, task)
			if err != nil {
				details := fmt.Sprintf("failed to handle task: %s", err)

				p.log.Errorf("failed to handle task '%s': %s", task.ID, details)

				changedTasks[task.ID] = db.Task{ID: task.ID, LogStatus: db.ErrorLoggingStatus}
				continue
			}

			if handled {
				changedTasks[task.ID] = updatedTask
			}
		}

		// всегда сохраняем проект, чтобы снять лок
		return len(changedTasks) > 0, p.database.SaveProjectWithTaskLogStatuses(ctx, project.DocumentID, changedTasks)
	})
}

func (p *SynchronizationLoggingController) handle(ctx context.Context, cred *db.LoggingCredentials, task db.Task) (db.Task, bool, error) {
	p.log.Infof("handling task '%s' with log status '%s'", task.ID, task.LogStatus)
	var requests []*logging.LoggingRequestResult
	var err error
	switch task.LogStatus {
	case db.SuccessLoggingStatus:
		return db.Task{}, false, nil
	case db.ErrorLoggingStatus:
		return db.Task{}, false, nil
	case db.CreationRequestLoggingStatus:
		requests, err = p.onCreateLoggingResource(&task, cred)
		if err != nil {
			return db.Task{}, false, fmt.Errorf("exception with generation found in task '%v': '%s", task, err)
		}
		p.log.Infof("perfroming task '%s' with log status '%s'", task.ID, task.LogStatus)
	case db.UpdateRequstLoggingStatus:
		p.log.Infof("handling task '%s' with log status '%s'", task.ID, task.LogStatus)
		requests, err = p.onPatchLoggingResource(&task, cred)
		if err != nil {
			return db.Task{}, false, fmt.Errorf("exception with generation found in task '%v': '%s", task, err)
		}
	default:
		return db.Task{}, false, fmt.Errorf("unknown status '%s' found in task '%v'", task.Status, task)

	}
	err = logging.PerformRequests(ctx, requests)
	if err != nil {
		return db.Task{}, false, fmt.Errorf("exception while performing requests '%v': %s", task, err)
	}
	for _, r := range requests {
		if r != nil {
			p.log.Infof("request recieved %s", r.Responce.String())
		}
	}
	for _, r := range requests {
		if r != nil {
			p.log.Debugf("request recieved %v", *r.Responce)
		}
	}

	return db.Task{ID: task.ID, LogStatus: db.SuccessLoggingStatus}, true, nil

}

func (p *SynchronizationLoggingController) onCreateLoggingResource(task *db.Task, cred *db.LoggingCredentials) ([]*logging.LoggingRequestResult, error) {
	p.log.Debugf("proceed creatoin-request for task '%s'", task.ID)
	countResources := len(task.Compose.Services)
	result := make([]*logging.LoggingRequestResult, countResources)

	roleName := task.RN.ToIsolationContext().StringWithDelimeter('-')
	indexAlias := getOpensearchIndexPatterns(task.RN, string(task.TN), task.Compose, true)
	indexPatterns := make([]string, 0)
	for _, ia := range indexAlias {
		indexPatterns = append(indexPatterns, ia.Index)
	}

	result = append(result, p.logging.CreateRole(roleName, indexPatterns))
	result = append(result, p.logging.CreateUser(cred.User, cred.Password))
	result = append(result, p.logging.MapUserRole(cred.User, roleName))
	dashBoardClient := logging.GetLoggingDashboardApiClient(p.properties, cred.User, cred.Password)
	for _, ia := range indexAlias {
		result = append(result, p.logging.CreateIndexPattern(ia.Index, "time", dashBoardClient))
	}

	return result, nil
}

func (p *SynchronizationLoggingController) onPatchLoggingResource(task *db.Task, cred *db.LoggingCredentials) ([]*logging.LoggingRequestResult, error) {
	p.log.Debugf("proceed patch-request for task '%s'", task.ID)
	countResources := len(task.Compose.Services)
	result := make([]*logging.LoggingRequestResult, countResources)

	roleName := task.RN.ToIsolationContext().StringWithDelimeter('-')
	indexAlias := getOpensearchIndexPatterns(task.RN, string(task.TN), task.Compose, false)
	indexPatterns := make([]string, 0)
	for _, ia := range indexAlias {
		indexPatterns = append(indexPatterns, ia.Index)
	}
	result = append(result, p.logging.UpdateRole(roleName, indexPatterns))
	dashBoardClient := logging.GetLoggingDashboardApiClient(p.properties, cred.User, cred.Password)
	for _, ia := range indexAlias {
		result = append(result, p.logging.CreateIndexPattern(ia.Index, "time", dashBoardClient))
	}

	return result, nil
}

func getOpensearchIndexPatterns(rn resourcename.ResourceName, tn string, compose *model.Compose, isNew bool) []logging.LoggingIndexAlias {
	result := make([]logging.LoggingIndexAlias, 0)

	for service := range compose.Services {
		result = append(result, logging.LoggingIndexAlias{Index: fmt.Sprintf("%s-%s*", rn.StringWithDelimeter('-'), service),
			Alias: fmt.Sprintf("%s_%s", tn, service)})
	}
	if isNew {
		result = append(result, logging.LoggingIndexAlias{Index: rn.StringWithDelimeter('-') + "*",
			Alias: tn})
	}
	if isNew {
		result = append(result, logging.LoggingIndexAlias{Index: rn.StringWithDelimeter('-') + "*",
			Alias: tn})
	}

	return result

}
